package cn.sherer.utils;

import java.util.Map;
import java.util.List;
import java.util.HashMap;
import java.util.ArrayList;
import cn.sherer.beans.douyin.*;
import cn.sherer.beans.douyin.yun.*;
import cn.sherer.beans.douyin.playlet.*;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;

import static cn.sherer.utils.WebUtils.*;
import static com.bytedance.microapp.Sign.*;
import static java.text.MessageFormat.format;

/**
 * 抖音工具类
 */
public class DouyinUtils {

    // <editor-fold defaultstate="collapsed" desc="接口地址">

    /** 接口调用凭据地址（网站、APP） */
    public static final String TOKEN_URL = "https://open.douyin.com/oauth/client_token/";
    /** 接口调用凭据地址（小程序） */
    public static final String MP_TOKEN_URL = "https://developer.toutiao.com/api/apps/v2/token";
    /** 接口调用凭据地址（小程序）（沙盒） */
    public static final String MP_SANDBOX_TOKEN_URL = "https://open-sandbox.douyin.com/api/apps/v2/token";

    /** 登录地址（网站、APP） */
    public static final String LOGIN_URL = "https://open.douyin.com/oauth/access_token/";
    /** 登录地址（小程序） */
    public static final String MP_LOGIN_URL = "https://developer.toutiao.com/api/apps/v2/jscode2session";
    /** 登录地址（小程序）（沙盒） */
    public static final String MP_SANDBOX_LOGIN_URL = "https://open-sandbox.douyin.com/api/apps/v2/jscode2session";

    /** 刷新令牌地址（网站、APP） */
    public static final String REFRESH_TOKEN_URL = "https://open.douyin.com/oauth/renew_refresh_token/";

    /** 用户信息地址（网站、APP） */
    public static final String USERINFO_URL = "https://open.douyin.com/oauth/userinfo/";

    /** 获取小程序的模版列表地址 */
    public static final String GET_SUBSCRIBE_TEMPLATE_URL = "https://developer.toutiao.com/api/apps/v1/capacity/query_app_subscribe_notification_tpl";

    /** 预下单接口地址 */
    public static final String CREATE_ORDER_URL = "https://developer.toutiao.com/api/apps/ecpay/v1/create_order";
    /** 预下单接口地址（沙盒） */
    public static final String SANDBOX_CREATE_ORDER_URL = "https://open-sandbox.douyin.com/api/apps/ecpay/v1/create_order";
    
    /** 支付结果查询地址 */
    public static final String QUERY_ORDER_URL = "https://developer.toutiao.com/api/apps/ecpay/v1/query_order";
    /** 支付结果查询地址（沙盒） */
    public static final String SANDBOX_QUERY_ORDER_URL = "https://open-sandbox.douyin.com/api/apps/ecpay/v1/query_order";

    /** 同步订单地址 */
    public static final String PUSH_ORDER_URL = "https://developer.toutiao.com/api/apps/order/v2/push";

    /** 查询订单信息（交易系统） */
    public static final String QUERY_ORDER_INFO_URL = "https://open.douyin.com/api/trade_basic/v1/developer/order_query/";

    /** 发起退款地址（交易系统） */
    public static final String CREATE_REFUND_URL = "https://open.douyin.com/api/trade_basic/v1/developer/refund_create/";
    /** 查询退款信息地址（交易系统） */
    public static final String QUERY_REFUND_INFO_URL = "https://open.douyin.com/api/trade_basic/v1/developer/refund_query/";
    /** 同步退款审核结果地址（交易系统） */
    public static final String AUDIT_REFUND_URL = "https://open.douyin.com/api/trade_basic/v1/developer/refund_audit_callback/";

    /** 推送履约状态（交易系统） */
    public static final String PUSH_FULFILL_STATUS_URL = "https://open.douyin.com/api/trade_basic/v1/developer/fulfill_push_status";

    /** 短剧短剧内容库：资源上传地址 */
    public static final String PLAYLET_RESOURCE_UPLOAD_URL = "https://open.douyin.com/api/playlet/v2/resource/upload/";
    /** 短剧短剧内容库：视频查询地址 */
    public static final String PLAYLET_VIDEO_QUERY_URL = "https://open.douyin.com/api/playlet/v2/video/query/";
    /** 短剧短剧内容库：创建短剧地址 */
    public static final String PLAYLET_VIDEO_CREATE_URL = "https://open.douyin.com/api/playlet/v2/video/create/";
    /** 短剧短剧内容库：编辑短剧/添加剧集地址 */
    public static final String PLAYLET_VIDEO_EDIT_URL = "https://open.douyin.com/api/playlet/v2/video/edit/";
    /** 短剧短剧内容库：授权地址 */
    public static final String PLAYLET_AUTHORIZE_URL = "https://open.douyin.com/api/playlet/v2/auth/authorize/";
    /** 短剧短剧内容库：授权查询地址 */
    public static final String PLAYLET_AUTHORIZE_QUERY_URL = "https://open.douyin.com/api/playlet/v2/auth/authorize/query/";
    /** 短剧短剧内容库：短剧绑定地址 */
    public static final String PLAYLET_ALBUM_BIND_URL = "https://open.douyin.com/api/playlet/v2/album/bind/";
    /** 短剧短剧内容库：短剧送审地址 */
    public static final String PLAYLET_VIDEO_REVIEW_URL = "https://open.douyin.com/api/playlet/v2/video/review/";
    /** 短剧短剧内容库：短剧上线地址 */
    public static final String PLAYLET_ALBUM_ONLINE_URL = "https://open.douyin.com/api/playlet/v2/album/online/";
    /** 短剧短剧内容库：短剧查询地址 */
    public static final String PLAYLET_ALBUM_FETCH_URL = "https://open.douyin.com/api/playlet/v2/album/fetch/";
    /** 短剧短剧内容库：短剧别名地址 */
    public static final String PLAYLET_ALIAS_BIND_URL = "https://open.douyin.com/api/playlet/v2/alias/bind/";
    /** 短剧短剧内容库：短剧播放信息地址 */
    public static final String PLAYLET_VIDEO_PLAY_INFO_URL = "https://open.douyin.com/api/playlet/v2/video/play_info/";
    /** 短剧短剧内容库：短剧后置审核查询地址 */
    public static final String RESOURCE_LIBRARY_POST_REVIEW_QUERY_URL = "https://open.douyin.com/api/resource_library/v1/post_review/query/";
    /** 短剧短剧内容库：小程序维度path绑定地址 */
    public static final String RESOURCE_LIBRARY_APP_PATH_BIND_URL = "https://open.douyin.com/api/resource_library/v1/app/path/bind/";
    /** 短剧短剧内容库：小程序维度path绑定查询地址 */
    public static final String RESOURCE_LIBRARY_APP_PATH_QUERY_URL = "https://open.douyin.com/api/resource_library/v1/app/path/query/";
    /** 短剧短剧内容库：短剧用户行为地址 */
    public static final String RESOURCE_LIBRARY_USER_ACTION_URL = "https://open.douyin.com/api/resource_library/v1/user_action/";
    /** 短剧短剧内容库：短剧视频同步抖音云地址 */
    public static final String PLAYLET_VIDEO_SYNC_CLOUD_URL = "https://open.douyin.com/api/playlet/v2/video/sync_to_cloud/";
    /** 短剧短剧内容库：短剧端内分发可见性限制操作地址 */
    public static final String RESOURCE_LIBRARY_DISTRIBUTION_OPERATE_URL = "https://open.douyin.com/api/resource_library/v1/distribution/operate/";
    /** 短剧短剧内容库：短剧端内分发可见性限制查询地址 */
    public static final String RESOURCE_LIBRARY_DISTRIBUTION_QUERY_URL = "https://open.douyin.com/api/resource_library/v1/distribution/query/";
    /** 短剧短剧内容库：小程序维度交易权益数据查询地址 */
    public static final String RESOURCE_LIBRARY_IAA_TRADE_DATA_QUERY_URL = "https://open.douyin.com/api/resource_library/v1/iaa/trade_data/query/";

    /** 抖音云-媒资管理：分页获取媒资列表地址 */
    public static final String YUN_VOC_GET_VIDEO_LIST_URL = "https://open.douyin.com/api/dyc_voc/get_video_list";
    /** 抖音云-媒资管理：删除媒资地址 */
    public static final String YUN_VOC_DELETE_VIDEO_URL = "https://open.douyin.com/api/dyc_voc/delete_video";
    /** 抖音云-媒资管理：获取媒资播放链接地址 */
    public static final String YUN_VOC_GET_VIDEO_URL = "https://open.douyin.com/api/dyc_voc/get_video_by_vid";
    /** 抖音云-媒资管理：批量上传视频地址 */
    public static final String YUN_VOC_UPLOAD_VIDEO_URL = "https://open.douyin.com/api/dyc_voc/upload_video_by_urls";

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="枚举类型">

    /** 应用类型 */
    public enum AppType {
        /** 小程序 */
        MP,
        /** 应用内 */
        WEB,
        /** 网站 */
        H5,
        /** APP */
        APP,
    }

    /** 短剧资源上传视频格式 */
    public enum PlayletResourceUploadVideoFormat {
        MP4,
        M3U8,
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="AccessToken">

    /**
     * 获取接口调用凭据（网站、APP）
     * @param client_key 应用ID
     * @param client_secret 应用密钥
     * @return 接口调用凭据
     */
    public static DyResult<DyToken> getAccessToken(String client_key, String client_secret) {
        return getAccessToken(AppType.APP, client_key, client_secret);
    }

    /**
     * 获取接口调用凭据（小程序）
     * @param appid 应用ID
     * @param secret 应用密钥
     * @return 接口调用凭据
     */
    public static DyResult<DyToken> getMPAccessToken(String appid, String secret) {
        return getAccessToken(AppType.MP, appid, secret);
    }

    /**
     * 获取接口调用凭据
     * @param type 应用类型
     * @param appid 应用ID
     * @param secret 应用密钥
     * @return 接口调用凭据
     */
    public static DyResult<DyToken> getAccessToken(AppType type, String appid, String secret) {
        String url = "", param = "";
        Map<String, String> headers = new HashMap<>();
        if (type == AppType.MP) {
            url = MP_TOKEN_URL;
            headers.put("Content-Type", "application/json");
            param = new JSONObject() {{ put("appid", appid); put("secret", secret); put("grant_type", "client_credential"); }}.toJSONString();
        } else {
            url = TOKEN_URL;
            param = format("client_key={0}&client_secret={1}&grant_type={2}", appid, secret, "client_credential");
        }
        String result = post(url, param, DOUYIN_AGENT, headers);
        System.out.println("抖音凭据结果：" + result);
        DyResult<DyToken> dyResult = JSONObject.parseObject(result, new TypeReference<DyResult<DyToken>>() {});
        if (type == AppType.MP && dyResult.getData() != null) {
            dyResult.getData().setErr_no(dyResult.getErr_no());
            dyResult.getData().setErr_tips(dyResult.getErr_tips());
        } else if (dyResult.getData() != null) {
            dyResult.setErr_no(dyResult.getData().getErr_no());
            dyResult.setErr_tips(dyResult.getData().getErr_tips());
        }
        return dyResult;
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="登录">

    /**
     * 获取登录凭证（网站、APP）
     * @param client_key 应用ID
     * @param client_secret 应用密钥
     * @param code 登录时获取的 code
     * @return 登录凭证
     */
    public static DyResult<DyLogin> getLogin(String client_key, String client_secret, String code) {
        return getLogin(AppType.APP, client_key, client_secret, code);
    }

    /**
     * 获取小程序登录凭证
     * @param appid 应用ID
     * @param secret 应用密钥
     * @param code 登录时获取的 code
     * @return 登录凭证
     */
    public static DyResult<DyLogin> getMPLogin(String appid, String secret, String code) {
        return getMPLogin(appid, secret, code, "");
    }

    /**
     * 获取小程序登录凭证
     * @param appid 应用ID
     * @param secret 应用密钥
     * @param code 登录时获取的 code
     * @param anonymous_code 匿名登录时获取的 anonymous_code
     * @return 登录凭证
     */
    public static DyResult<DyLogin> getMPLogin(String appid, String secret, String code, String anonymous_code) {
        return getLogin(AppType.MP, appid, secret, code, anonymous_code);
    }

    /**
     * 获取登录凭证
     * @param type 应用类型
     * @param appid 应用ID（client_key）
     * @param secret 应用密钥（client_secret）
     * @param code 登录时获取的 code
     * @return 登录凭证
     */
    public static DyResult<DyLogin> getLogin(AppType type, String appid, String secret, String code) {
        return getLogin(type, appid, secret, code, "");
    }

    /**
     * 获取登录凭证
     * @param type 应用类型
     * @param appid 应用ID（client_key）
     * @param secret 应用密钥（client_secret）
     * @param code 登录时获取的 code
     * @param anonymous_code 匿名登录时获取的 anonymous_code
     * @return 登录凭证
     */
    public static DyResult<DyLogin> getLogin(AppType type, String appid, String secret, String code, String anonymous_code) {
        String url = "", param = "";
        Map<String, String> headers = new HashMap<>();
        if (type == AppType.MP) {
            url = MP_LOGIN_URL;
            headers.put("Content-Type", "application/json");
            param = new JSONObject() {{ put("appid", appid); put("secret", secret); put("code", code); put("anonymous_code", anonymous_code); }}.toJSONString();
        } else {
            url = LOGIN_URL;
            param = format("client_key={0}&client_secret={1}&code={2}&grant_type={3}", appid, secret, code, "authorization_code");
        }
        String result = post(url, param, DOUYIN_AGENT, headers);
        System.out.println("抖音登录结果：" + result);
        DyResult<DyLogin> dyResult = JSONObject.parseObject(result, new TypeReference<DyResult<DyLogin>>() {});
        if (type == AppType.MP && dyResult.getData() != null) {
            dyResult.getData().setErr_no(dyResult.getErr_no());
            dyResult.getData().setErr_tips(dyResult.getErr_tips());
        } else if (dyResult.getData() != null) {
            dyResult.setErr_no(dyResult.getData().getErr_no());
            dyResult.setErr_tips(dyResult.getData().getErr_tips());
        }
        return dyResult;
    }

    /**
     * 刷新登录凭证（网站、APP）
     * @param client_key 应用ID
     * @param refreshToken 刷新令牌
     * @return 登录凭证
     */
    public static DyResult<DyLogin> refreshLogin(String client_key, String refreshToken) {
        String result = post(REFRESH_TOKEN_URL, format("client_key={0}&refresh_token={1}", client_key, refreshToken), DOUYIN_AGENT);
        System.out.println("抖音刷新登录凭证结果：" + result);
        DyResult<DyLogin> dyResult = JSONObject.parseObject(result, new TypeReference<DyResult<DyLogin>>() {});
        if (dyResult.getData() != null) {
            dyResult.setErr_no(dyResult.getData().getErr_no());
            dyResult.setErr_tips(dyResult.getData().getErr_tips());
        }
        return dyResult;
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="用户信息">

    /**
     * 获取用户信息（网站、APP）
     * @param token 接口调用凭据
     * @param openid 用户的唯一标识
     * @return 用户信息
     */
    public static DyResult<DyUser> getUserInfo(String token, String openid) {
        String result = post(USERINFO_URL, format("access_token={0}&open_id={1}", token, openid), DOUYIN_AGENT);
        System.out.println("抖音获取用户信息结果：" + result);
        DyResult<DyUser> dyResult = JSONObject.parseObject(result, new TypeReference<DyResult<DyUser>>() {});
        if (dyResult.getData() != null) {
            dyResult.setErr_no(dyResult.getData().getErr_no());
            dyResult.setErr_tips(dyResult.getData().getErr_tips());
        }
        return dyResult;
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="订阅消息">

    /**
     * 获取小程序的模版列表地址
     * @param token 接口调用凭据
     * @param page_num 分页编号，从 1 开始
     * @param page_size 分页大小，小于等于 50
     * @param classification 订阅消息类型；1: 一次性订阅消息；2: 长期性订阅消息
     * @param category_ids 服务类目id，可以通过获取已设置的服务类目接口获取小程序的服务类目，只能是已经审核通过的服务类目
     * @return 订阅消息模板列表
     */
    public static DyResult<DySubscribeTemplateResult> getSubscribeTemplateList(String token, int page_num, int page_size, int classification, String... category_ids) {
        String url = GET_SUBSCRIBE_TEMPLATE_URL;
        String categorys = category_ids == null || category_ids.length == 0 ? "" : (category_ids.length == 1 ? category_ids[0] : String.join(",", category_ids));
        String param = format("page_num={0}&page_size={1}&classification={2}&category_ids={3}", page_num, page_size, classification, categorys);
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);
        String result = get(url + "?" + param, DOUYIN_AGENT, headers);
        System.out.println("抖音小程序的模版列表结果：" + result);
        DyResult<DySubscribeTemplateResult> dyResult = JSONObject.parseObject(result, new TypeReference<DyResult<DySubscribeTemplateResult>>() {});
        dyResult.getData().setErr_no(dyResult.getErr_no());
        dyResult.getData().setErr_tips(dyResult.getErr_tips());
        return dyResult;
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="担保支付">

    /**
     * 预下单
     * @param data 预下单数据
     * @param SALT 支付密钥值
     * @return 预下单结果
     */
    public static DyResult<DyOrder> createOrder(Map<String, Object> data, String SALT) {
        data.put("sign", requestSign(data, SALT));
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        String result = post(CREATE_ORDER_URL, JSONObject.toJSONString(data), DOUYIN_AGENT, headers);
        System.out.println("抖音预下单结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyOrder>>() {});
    }

    /**
     * 预下单（沙盒）
     * @param data 预下单数据
     * @param SALT 支付密钥值
     * @return 预下单结果
     */
    public static DyResult<DyOrder> createSandboxOrder(Map<String, Object> data, String SALT) {
        data.put("sign", requestSign(data, SALT));
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        String result = post(SANDBOX_CREATE_ORDER_URL, JSONObject.toJSONString(data), DOUYIN_AGENT, headers);
        System.out.println("抖音预下单结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyOrder>>() {});
    }

    /**
     * 查询订单
     * @param app_id 小程序APPID
     * @param out_order_no 开发者侧的订单号, 同一小程序下不可重复
     * @param SALT 支付密钥值
     * @return 查询订单结果
     */
    public static DyResult<DyOrderResult> queryOrder(String app_id, String out_order_no, String SALT) {
        return queryOrder(app_id, out_order_no, null, SALT);
    }

    /**
     * 查询订单
     * @param app_id 小程序APPID
     * @param out_order_no 开发者侧的订单号, 同一小程序下不可重复
     * @param thirdparty_id 第三方平台服务商 id，非服务商模式留空
     * @param SALT 支付密钥值
     * @return 查询订单结果
     */
    public static DyResult<DyOrderResult> queryOrder(String app_id, String out_order_no, String thirdparty_id, String SALT) {
        Map<String, Object> data = new HashMap<String, Object>() {{ put("app_id", app_id); put("out_order_no", out_order_no); }};
        if (thirdparty_id != null && !thirdparty_id.isEmpty()) { data.put("thirdparty_id", thirdparty_id); }
        data.put("sign", requestSign(data, SALT));
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        String result = post(QUERY_ORDER_URL, JSONObject.toJSONString(data), DOUYIN_AGENT, headers);
        System.out.println("抖音查询订单结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyOrderResult>>() {});
    }

    /**
     * 查询订单（沙盒）
     * @param app_id 小程序APPID
     * @param out_order_no 开发者侧的订单号, 同一小程序下不可重复
     * @param SALT 支付密钥值
     * @return 查询订单结果
     */
    public static DyResult<DyOrderResult> querySandboxOrder(String app_id, String out_order_no, String SALT) {
        return querySandboxOrder(app_id, out_order_no, null, SALT);
    }

    /**
     * 查询订单（沙盒）
     * @param app_id 小程序APPID
     * @param out_order_no 开发者侧的订单号, 同一小程序下不可重复
     * @param thirdparty_id 第三方平台服务商 id，非服务商模式留空
     * @param SALT 支付密钥值
     * @return 查询订单结果
     */
    public static DyResult<DyOrderResult> querySandboxOrder(String app_id, String out_order_no, String thirdparty_id, String SALT) {
        Map<String, Object> data = new HashMap<String, Object>() {{ put("app_id", app_id); put("out_order_no", out_order_no); }};
        if (thirdparty_id != null && !thirdparty_id.isEmpty()) { data.put("thirdparty_id", thirdparty_id); }
        data.put("sign", requestSign(data, SALT));
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        String result = post(SANDBOX_QUERY_ORDER_URL, JSONObject.toJSONString(data), DOUYIN_AGENT, headers);
        System.out.println("抖音查询订单结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyOrderResult>>() {});
    }

    /**
     * 订单同步
     * @param data 订单同步数据
     * @return 订单同步结果
     */
    public static DyResult<String> pushOrder(Map<String, Object> data) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        String result = post(PUSH_ORDER_URL, JSONObject.toJSONString(data), DOUYIN_AGENT, headers);
        System.out.println("抖音订单同步结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<String>>() {});
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="订单（交易系统）">

    /**
     * 查询订单信息（交易系统）
     * order_id , out_order_no 二选一，不能都不填。
     * https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/trade-system/general/order/query_order
     * 
     * @param token 接口调用凭据
     * @param order_id 交易订单号
     * @param out_order_no 开发者的单号
     * @return 订单查询结果
     */
    public static DyResult<DyOrderMsg> queryOrderInfo(String token, String order_id, String out_order_no) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);
        JSONObject data = new JSONObject();
        if (order_id != null && !order_id.trim().isEmpty()) { data.put("order_id", order_id); }
        if (out_order_no != null && !out_order_no.trim().isEmpty()) { data.put("out_order_no", out_order_no); }
        String result = post(QUERY_ORDER_INFO_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("抖音交易系统查询退款结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyOrderMsg>>() {});
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="退款（交易系统）">

    /**
     * 发起退款（交易系统）
     * https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/trade-system/general/refund/create_refund
     * 
     * @param token 接口调用凭据
     * @param data 退款数据
     * @return 退款结果
     */
    public static DyResult<DyRefund> createRefund(String token, Map<String, Object> data) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);
        String result = post(CREATE_REFUND_URL, JSONObject.toJSONString(data), DOUYIN_AGENT, headers);
        System.out.println("抖音交易系统发起退款结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyRefund>>() {});
    }

    /**
     * 查询退款（交易系统）
     * 意：refund_id , out_refund_no , order_id 三选一，不能都不填。
     * https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/trade-system/general/refund/query_refund
     * 
     * @param token 接口调用凭据
     * @param refund_id 抖音开平内部交易退款单号
     * @param out_refund_no 开发者系统生成的退款单号
     * @param order_id 抖音开平内部交易订单号
     * @return 退款查询结果
     */
    public static DyResult<DyRefundResult> queryRefundInfo(String token, String refund_id, String out_refund_no, String order_id) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);
        JSONObject data = new JSONObject();
        if (refund_id != null && !refund_id.trim().isEmpty()) { data.put("refund_id", refund_id); }
        if (out_refund_no != null && !out_refund_no.trim().isEmpty()) { data.put("out_refund_no", out_refund_no); }
        if (order_id != null && !order_id.trim().isEmpty()) { data.put("order_id", order_id); }
        String result = post(QUERY_REFUND_INFO_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("抖音交易系统查询退款结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyRefundResult>>() {});
    }

    /**
     * 同步退款审核结果（交易系统）
     * https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/trade-system/general/refund/audit_refund
     * 
     * @param token 接口调用凭据
     * @param refund_id 交易系统侧退款单号
     * @param refund_audit_status 退款审核状态：true 通过，false 拒绝
     * @param deny_message 不同意退款信息(不同意退款时必填)，长度 <= 512 byte
     * @return 退款审核结果
     */
    public static DyResult<Object> auditRefund(String token, String refund_id, boolean refund_audit_status, String deny_message) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);
        JSONObject data = new JSONObject();
        data.put("refund_id", refund_id);
        data.put("refund_audit_status", refund_audit_status == true ? 1 : 2);
        if (deny_message != null && !deny_message.trim().isEmpty()) { data.put("deny_message", deny_message); }
        String result = post(AUDIT_REFUND_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("抖音交易系统同步退款审核结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<Object>>() {});
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="履约（交易系统）">

    /**
     * 推送履约状态（交易系统）
     * https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/trade-system/general/trade_fulfill/push_fulfill_statsus
     * 
     * @param token 接口调用凭据
     * @param order_id 通用交易系统侧订单号，长度 <= 64 byte
     * @param to_status 目标状态：fulfilling - 履约中；fulfill_done - 履约完成
     * @param item_order_id_list 通用交易系统item单号，每个单号的长度 <= 64 byte。注：可通过订单查询接口获得
     * @return 履约结果
     */
    public static DyResult<Object> pushFulfillStatus(String token, String order_id, String to_status, List<String> item_order_id_list) {
        item_order_id_list = item_order_id_list == null ? new ArrayList<>() : item_order_id_list;
        return pushFulfillStatus(token, order_id, to_status, item_order_id_list.toArray(new String[0]));
    }

    /**
     * 推送履约状态（交易系统）
     * https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/server/trade-system/general/trade_fulfill/push_fulfill_statsus
     * 
     * @param token 接口调用凭据
     * @param order_id 通用交易系统侧订单号，长度 <= 64 byte
     * @param to_status 目标状态：fulfilling - 履约中；fulfill_done - 履约完成
     * @param item_order_id_list 通用交易系统item单号，每个单号的长度 <= 64 byte。注：可通过订单查询接口获得
     * @return 履约结果
     */
    public static DyResult<Object> pushFulfillStatus(String token, String order_id, String to_status, String... item_order_id_list) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);
        JSONObject data = new JSONObject();
        data.put("order_id", order_id);
        data.put("to_status", to_status);
        if (item_order_id_list != null) { data.put("item_order_id_list", item_order_id_list); }
        String result = post(PUSH_FULFILL_STATUS_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("抖音交易系统推送履约状态结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<Object>>() {});
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="短剧内容库">

    /**
     * 短剧内容库：上传图片（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#VWkZdkvGsog2CDxKh8wcBXCqnme
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param url 图片地址
     * @return 上传图片信息
     */
    public static DyResult<DyPlayletResourceUploadResult> uploadPlayletResourceImage(String token, String ma_app_id, String url) {
        return uploadPlayletResourceImage(token, ma_app_id, new DyPlayletResourceImageMeta(url));
    }

    /**
     * 短剧内容库：上传图片（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#VWkZdkvGsog2CDxKh8wcBXCqnme
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param imageMeta 图片信息
     * @return 上传图片信息
     */
    public static DyResult<DyPlayletResourceUploadResult> uploadPlayletResourceImage(String token, String ma_app_id, DyPlayletResourceImageMeta imageMeta) {
        Map<String, Object> image_meta = JSONObject.parseObject(JSONObject.toJSONString(imageMeta), new TypeReference<Map<String, Object>>() {});
        return uploadPlayletResourceImage(token, ma_app_id, image_meta);
    }

    /**
     * 短剧内容库：上传图片（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#VWkZdkvGsog2CDxKh8wcBXCqnme
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param image_meta 图片信息
     * @return 上传图片信息
     */
    public static DyResult<DyPlayletResourceUploadResult> uploadPlayletResourceImage(String token, String ma_app_id, Map<String, Object> image_meta) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("ma_app_id", ma_app_id);
        data.put("resource_type", 2);
        data.put("image_meta", image_meta);

        String result = post(PLAYLET_RESOURCE_UPLOAD_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：上传图片结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyPlayletResourceUploadResult>>() {});
    }


    /**
     * 短剧内容库：上传视频（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#KtfedkurXoLuGTx3ihGca5xhnKg
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param title 视频标题
     * @param description 视频简介
     * @param format 视频格式
     * @param dy_cloud_id 抖音云的视频 ID；用于从抖音云同步视频到内容库
     * @return 上传视频信息
     */
    public static DyResult<DyPlayletResourceUploadResult> uploadPlayletResourceVideo(String token, String ma_app_id, String title, String description, PlayletResourceUploadVideoFormat format, String dy_cloud_id) {
        return uploadPlayletResourceVideo(token, ma_app_id, new DyPlayletResourceVideoMeta(title, description, format.name().toLowerCase(), dy_cloud_id));
    }

    /**
     * 短剧内容库：上传视频（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#KtfedkurXoLuGTx3ihGca5xhnKg
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param url 视频地址
     * @param title 视频标题
     * @param description 视频简介
     * @param format 视频格式
     * @param use_dy_cloud 是否同步至抖音云；传此字段之前需要提前配置好抖音云
     * @return 上传视频信息
     */
    public static DyResult<DyPlayletResourceUploadResult> uploadPlayletResourceVideo(String token, String ma_app_id, String url, String title, String description, PlayletResourceUploadVideoFormat format, boolean use_dy_cloud) {
        return uploadPlayletResourceVideo(token, ma_app_id, new DyPlayletResourceVideoMeta(url, title, description, format.name().toLowerCase(), use_dy_cloud));
    }

    /**
     * 短剧内容库：上传视频（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#VWkZdkvGsog2CDxKh8wcBXCqnme
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param videoMeta 视频信息
     * @return 上传视频信息
     */
    public static DyResult<DyPlayletResourceUploadResult> uploadPlayletResourceVideo(String token, String ma_app_id, DyPlayletResourceVideoMeta videoMeta) {
        Map<String, Object> video_meta = JSONObject.parseObject(JSONObject.toJSONString(videoMeta), new TypeReference<Map<String, Object>>() {});
        return uploadPlayletResourceVideo(token, ma_app_id, video_meta);
    }

    /**
     * 短剧内容库：上传视频（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#VWkZdkvGsog2CDxKh8wcBXCqnme
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param video_meta 视频信息
     * @return 上传视频信息
     */
    public static DyResult<DyPlayletResourceUploadResult> uploadPlayletResourceVideo(String token, String ma_app_id, Map<String, Object> video_meta) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("ma_app_id", ma_app_id);
        data.put("resource_type", 1);
        data.put("video_meta", video_meta);

        String result = post(PLAYLET_RESOURCE_UPLOAD_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：上传视频结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyPlayletResourceUploadResult>>() {});
    }


    /**
     * 短剧内容库：按抖音开放平台的视频查询视频状态
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#WgFxdW7bQoF0Umx88ibc7Hd5n2g
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param open_video_id 抖音开放平台的视频id
     * @return 视频状态信息
     */
    public static DyResult<DyPlayletVideoUploadStatusResult> queryPlayletVideoByOpen(String token, String ma_app_id, String open_video_id) {
        return queryPlayletVideo(token, ma_app_id, 1, open_video_id, null);
    }

    /**
     * 短剧内容库：按抖音云的视频查询视频状态
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param dy_cloud_id 抖音云的视频vid
     * @return 视频状态信息
     */
    public static DyResult<DyPlayletVideoUploadStatusResult> queryPlayletVideoByCloud(String token, String ma_app_id, String dy_cloud_id) {
        return queryPlayletVideo(token, ma_app_id, 2, null, dy_cloud_id);
    }

    /**
     * 短剧内容库：查询视频状态
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param video_id_type 查询方式：1-通过 open_video_id 查询；2-通过抖音云的视频 vid 查询；
     * @param open_video_id 抖音开放平台的视频id，video_id_type 为 1 必填
     * @param dy_cloud_id 抖音云的视频vid，video_id_type 为 2 必填
     * @return 视频状态信息
     */
    public static DyResult<DyPlayletVideoUploadStatusResult> queryPlayletVideo(String token, String ma_app_id, int video_id_type, String open_video_id, String dy_cloud_id) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("ma_app_id", ma_app_id);
        data.put("video_id_type", video_id_type);
        if (dy_cloud_id != null && !dy_cloud_id.trim().isEmpty()) data.put("dy_cloud_id", dy_cloud_id);
        if (open_video_id != null && !open_video_id.trim().isEmpty()) data.put("open_video_id", open_video_id);

        String result = post(PLAYLET_VIDEO_QUERY_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：查询视频状态结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyPlayletVideoUploadStatusResult>>() {});
    }


    /**
     * 短剧内容库：创建短剧（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#HvRodFdQXok7B6xsWQocWSNmnHb
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param albumInfo 短剧信息
     * @return 创建短剧信息
     */
    public static DyResult<DyPlayletAlbumResult> createPlayletVideo(String token, String ma_app_id, DyPlayletAlbumInfo albumInfo) {
        Map<String, Object> album_info = JSONObject.parseObject(JSONObject.toJSONString(albumInfo), new TypeReference<Map<String, Object>>() {});
        return createPlayletVideo(token, ma_app_id, album_info);
    }

    /**
     * 短剧内容库：创建短剧（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#HvRodFdQXok7B6xsWQocWSNmnHb
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param album_info 短剧信息
     * @return 创建短剧信息
     */
    public static DyResult<DyPlayletAlbumResult> createPlayletVideo(String token, String ma_app_id, Map<String, Object> album_info) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("ma_app_id", ma_app_id);
        data.put("album_info", album_info);

        String result = post(PLAYLET_VIDEO_CREATE_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：创建短剧结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyPlayletAlbumResult>>() {});
    }


    /**
     * 短剧内容库：编辑短剧（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#DldHd393hoC8AhxIyIncLpjXn1D
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param album_id 短剧id
     * @param albumInfo 短剧信息
     * @return 编辑短剧信息
     */
    public static DyResult<DyPlayletAlbumResult> editPlayletVideo(String token, String ma_app_id, long album_id, DyPlayletAlbumInfo albumInfo) {
        return editPlayletVideo(token, ma_app_id, album_id, albumInfo, null);
    }

    /**
     * 短剧内容库：编辑/添加剧集（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#DldHd393hoC8AhxIyIncLpjXn1D
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param album_id 短剧id
     * @param episodeInfoList 剧集列表
     * @return 编辑/添加剧集信息
     */
    public static DyResult<DyPlayletAlbumResult> editPlayletVideo(String token, String ma_app_id, long album_id, List<DyPlayletEpisodeInfo> episodeInfoList) {
        return editPlayletVideo(token, ma_app_id, album_id, null, episodeInfoList);
    }

    /**
     * 短剧内容库：编辑短剧/添加剧集（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#DldHd393hoC8AhxIyIncLpjXn1D
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param album_id 短剧id
     * @param albumInfo 短剧信息
     * @param episodeInfoList 剧集列表
     * @return 编辑短剧/添加剧集信息
     */
    public static DyResult<DyPlayletAlbumResult> editPlayletVideo(String token, String ma_app_id, long album_id, DyPlayletAlbumInfo albumInfo, List<DyPlayletEpisodeInfo> episodeInfoList) {
        Map<String, Object> album_info = albumInfo != null ? JSONObject.parseObject(JSONObject.toJSONString(albumInfo), new TypeReference<Map<String, Object>>() {}) : null;
        List<Object> episode_info_list = null;
        if (episodeInfoList != null && !episodeInfoList.isEmpty()) {
            episode_info_list = new ArrayList<>();
            for (DyPlayletEpisodeInfo episodeInfo : episodeInfoList) { episode_info_list.add(JSONObject.parseObject(JSONObject.toJSONString(episodeInfo), new TypeReference<Map<String, Object>>() {})); }
        }
        return editPlayletVideo(token, ma_app_id, album_id, album_info, episode_info_list);
    }

    /**
     * 短剧内容库：编辑短剧/添加剧集（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#DldHd393hoC8AhxIyIncLpjXn1D
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param album_id 短剧id
     * @param album_info 短剧信息
     * @param episode_info_list 剧集列表
     * @return 编辑短剧/添加剧集信息
     */
    public static DyResult<DyPlayletAlbumResult> editPlayletVideo(String token, String ma_app_id, long album_id, Map<String, Object> album_info, List<Object> episode_info_list) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("ma_app_id", ma_app_id);
        data.put("album_id", album_id);
        if (album_info != null && !album_info.isEmpty()) data.put("album_info", album_info);
        if (episode_info_list != null && !episode_info_list.isEmpty()) data.put("episode_info_list", episode_info_list);

        String result = post(PLAYLET_VIDEO_EDIT_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：编辑短剧/添加剧集结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyPlayletAlbumResult>>() {});
    }


    /**
     * 短剧内容库：设置短剧授权（原有授权被移除）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#VnRDd9pfFoI93wxCOBhciOvpnqe
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param album_id 短剧id
     * @param app_id_list 被授权小程序列表（上限500）
     * @return 设置短剧授权结果
     */
    public static DyResult<Object> setPlayletAuthorize(String token, String ma_app_id, long album_id, List<String> app_id_list) {
        return setPlayletAuthorize(token, ma_app_id, album_id, app_id_list, false, false);
    }

    /**
     * 短剧内容库：新增短剧授权
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#VnRDd9pfFoI93wxCOBhciOvpnqe
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param album_id 短剧id
     * @param app_id_list 被授权小程序列表（上限500）
     * @return 新增短剧授权结果
     */
    public static DyResult<Object> addPlayletAuthorize(String token, String ma_app_id, long album_id, List<String> app_id_list) {
        return setPlayletAuthorize(token, ma_app_id, album_id, app_id_list, true, false);
    }

    /**
     * 短剧内容库：移除短剧授权
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#VnRDd9pfFoI93wxCOBhciOvpnqe
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param album_id 短剧id
     * @param app_id_list 被移除授权小程序列表（上限500）
     * @return 移除短剧授权结果
     */
    public static DyResult<Object> removePlayletAuthorize(String token, String ma_app_id, long album_id, List<String> app_id_list) {
        return setPlayletAuthorize(token, ma_app_id, album_id, app_id_list, false, true);
    }

    /**
     * 短剧内容库：设置短剧授权
     * 1. 全量覆盖， increment和 remove都不填
     * 2. 追加新增， increment填true
     * 3. 移除授权， remove填true
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#VnRDd9pfFoI93wxCOBhciOvpnqe
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param album_id 短剧id
     * @param app_id_list 被授权小程序列表（上限500）
     * @param increment 是否采用追加式授权：false或者不传-默认逻辑；true-追加式授权，当前请求授权的app_id_list会在原有授权app_id_list基础上进行添加
     * @param remove 是否为移除授权：true-移除授权，当前请求授权的app_id_list会在原有授权app_id_list基础上进行删除
     * @return 设置短剧授权结果
     */
    public static DyResult<Object> setPlayletAuthorize(String token, String ma_app_id, long album_id, List<String> app_id_list, boolean increment, boolean remove) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("ma_app_id", ma_app_id);
        data.put("album_id", album_id);
        data.put("app_id_list", app_id_list);
        data.put("increment", increment);
        data.put("remove", remove);

        String result = post(PLAYLET_AUTHORIZE_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：设置短剧授权结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<Object>>() {});
    }


    /**
     * 短剧内容库：查询短剧授权
     * 1. 全量覆盖， increment和 remove都不填
     * 2. 追加新增， increment填true
     * 3. 移除授权， remove填true
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#VnRDd9pfFoI93wxCOBhciOvpnqe
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param album_id 短剧id
     * @return 设置短剧授权结果
     */
    public static DyResult<Object> queryPlayletAuthorize(String token, String ma_app_id, long album_id) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("ma_app_id", ma_app_id);
        data.put("album_id", album_id);

        String result = post(PLAYLET_AUTHORIZE_QUERY_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：查询短剧授权结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<Object>>() {});
    }


    /**
     * 短剧内容库：短剧页面绑定
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#Qst0dmzTPomsgexJkE8czh61nyf
     * @param token 接口调用凭据
     * @param singleSchemaBind 单集绑定数据
     * @param app_id 代操作小程序，若不传入此参数，则为token对应请求小程序进行操作
     * @return 设置短剧授权结果
     */
    public static DyResult<List<DyPlayletSingleSchemaBindInfo>> bindPlayletAlbum(String token, DyPlayletSingleSchemaBindInfo singleSchemaBind, String app_id) {
        return bindPlayletAlbum(token, 1, singleSchemaBind, null, 0, app_id);
    }

    /**
     * 短剧内容库：短剧页面绑定
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#Qst0dmzTPomsgexJkE8czh61nyf
     * @param token 接口调用凭据
     * @param single_schema_bind 单集绑定数据
     * @param app_id 代操作小程序，若不传入此参数，则为token对应请求小程序进行操作
     * @return 设置短剧授权结果
     */
    public static DyResult<List<DyPlayletSingleSchemaBindInfo>> bindPlayletAlbum(String token, Map<String, Object> single_schema_bind, String app_id) {
        return bindPlayletAlbum(token, 1, single_schema_bind, null, null, app_id);
    }

    /**
     * 短剧内容库：短剧页面绑定
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#Qst0dmzTPomsgexJkE8czh61nyf
     * @param token 接口调用凭据
     * @param schema_bind_list 批量绑定数据，不支持同时对多个短剧绑定，即album_id一致，最多操作200个剧集
     * @param app_id 代操作小程序，若不传入此参数，则为token对应请求小程序进行操作
     * @return 设置短剧授权结果
     */
    public static DyResult<List<DyPlayletSingleSchemaBindInfo>> bindPlayletAlbum(String token, List<Map<String, Object>> schema_bind_list, String app_id) {
        return bindPlayletAlbum(token, 2, null, schema_bind_list, null, app_id);
    }

    /**
     * 短剧内容库：短剧页面绑定
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#Qst0dmzTPomsgexJkE8czh61nyf
     * @param token 接口调用凭据
     * @param album_id 查询短剧id
     * @param app_id 代操作小程序，若不传入此参数，则为token对应请求小程序进行操作
     * @return 设置短剧授权结果
     */
    public static DyResult<List<DyPlayletSingleSchemaBindInfo>> queryPlayletAlbum(String token, long album_id, String app_id) {
        Map<String, Object> query_schema_bind = new HashMap<String, Object>() {{ put("album_id", album_id); }};
        return bindPlayletAlbum(token, 3, null, null, query_schema_bind, app_id);
    }

    /**
     * 短剧内容库：短剧页面绑定
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#Qst0dmzTPomsgexJkE8czh61nyf
     * @param token 接口调用凭据
     * @param query_schema_bind 查询绑定数据
     * @param app_id 代操作小程序，若不传入此参数，则为token对应请求小程序进行操作
     * @return 设置短剧授权结果
     */
    public static DyResult<List<DyPlayletSingleSchemaBindInfo>> queryPlayletAlbum(String token, Map<String, Object> query_schema_bind, String app_id) {
        return bindPlayletAlbum(token, 3, null, null, query_schema_bind, app_id);
    }

    /**
     * 短剧内容库：短剧页面绑定
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#Qst0dmzTPomsgexJkE8czh61nyf
     * @param token 接口调用凭据
     * @param schema_bind_type 绑定类型：1-单集绑定；2-批量绑定；3-查询
     * @param singleSchemaBind 单集绑定数据 schema_bind_type = 1 时必填
     * @param schemaBindList 批量绑定数据 schema_bind_type = 2 时必填，不支持同时对多个短剧绑定，即album_id一致，最多操作200个剧集
     * @param album_id 查询短剧id
     * @param app_id 代操作小程序，若不传入此参数，则为token对应请求小程序进行操作
     * @return 设置短剧授权结果
     */
    public static DyResult<List<DyPlayletSingleSchemaBindInfo>> bindPlayletAlbum(String token, int schema_bind_type, DyPlayletSingleSchemaBindInfo singleSchemaBind, List<DyPlayletSingleSchemaBindInfo> schemaBindList, long album_id, String app_id) {
        Map<String, Object> single_schema_bind = JSONObject.parseObject(JSONObject.toJSONString(singleSchemaBind), new TypeReference<Map<String, Object>>() {});
        List<Map<String, Object>> schema_bind_list = null;
        if (schemaBindList != null && !schemaBindList.isEmpty()) {
            schema_bind_list = new ArrayList<>();
            for (DyPlayletSingleSchemaBindInfo schemaBind : schemaBindList) { schema_bind_list.add(JSONObject.parseObject(JSONObject.toJSONString(schemaBind), new TypeReference<Map<String, Object>>() {})); }
        }
        Map<String, Object> query_schema_bind = album_id != 0 ? new HashMap<String, Object>() {{ put("album_id", album_id); }} : null;
        return bindPlayletAlbum(token, schema_bind_type, single_schema_bind, schema_bind_list, query_schema_bind, app_id);
    }

    /**
     * 短剧内容库：短剧页面绑定
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#Qst0dmzTPomsgexJkE8czh61nyf
     * @param token 接口调用凭据
     * @param schema_bind_type 绑定类型：1-单集绑定；2-批量绑定；3-查询
     * @param single_schema_bind 单集绑定数据 schema_bind_type = 1 时必填
     * @param schema_bind_list 批量绑定数据 schema_bind_type = 2 时必填，不支持同时对多个短剧绑定，即album_id一致，最多操作200个剧集
     * @param query_schema_bind 查询绑定数据 schema_bind_type = 3 时必填
     * @param app_id 代操作小程序，若不传入此参数，则为token对应请求小程序进行操作
     * @return 设置短剧授权结果
     */
    public static DyResult<List<DyPlayletSingleSchemaBindInfo>> bindPlayletAlbum(String token, int schema_bind_type, Map<String, Object> single_schema_bind, List<Map<String, Object>> schema_bind_list, Map<String, Object> query_schema_bind, String app_id) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("schema_bind_type", schema_bind_type);
        if (single_schema_bind != null && !single_schema_bind.isEmpty()) data.put("single_schema_bind", single_schema_bind);
        if (schema_bind_list != null && !schema_bind_list.isEmpty()) data.put("schema_bind_list", schema_bind_list);
        if (query_schema_bind != null && !query_schema_bind.isEmpty()) data.put("query_schema_bind", query_schema_bind);
        if (app_id != null && !app_id.trim().isEmpty()) data.put("app_id", app_id);

        String result = post(PLAYLET_ALBUM_BIND_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：短剧页面绑定结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<List<DyPlayletSingleSchemaBindInfo>>>() {});
    }


    /**
     * 短剧内容库：短剧送审
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#F9VfdaT2Go8bWXxOwk3c53i8n6f
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param album_id 短剧id
     * @return 短剧送审结果
     */
    public static DyResult<DyPlayletReviewResult> reviewPlayletVideo(String token, String ma_app_id, long album_id) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("ma_app_id", ma_app_id);
        data.put("album_id", album_id);

        String result = post(PLAYLET_VIDEO_REVIEW_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：短剧送审结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyPlayletReviewResult>>() {});
    }


    /**
     * 短剧内容库：查询短剧线上版本
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#LcDKdjDnEomNETx26F3cYOJXnc8
     * @param token 接口调用凭据
     * @param album_id 短剧id
     * @return 查询短剧线上版本
     */
    public static DyResult<DyPlayletOnlineResult> queryPlayletAlbumOnline(String token, long album_id) {
        return setPlayletAlbumOnline(token, album_id, 1, 0);
    }

    /**
     * 短剧内容库：短剧上线
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#LcDKdjDnEomNETx26F3cYOJXnc8
     * @param token 接口调用凭据
     * @param album_id 短剧id
     * @return 短剧上线结果
     */
    public static DyResult<DyPlayletOnlineResult> setPlayletAlbumOnline(String token, long album_id) {
        return setPlayletAlbumOnline(token, album_id, 0);
    }

    /**
     * 短剧内容库：短剧下线
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#LcDKdjDnEomNETx26F3cYOJXnc8
     * @param token 接口调用凭据
     * @param album_id 短剧id
     * @return 短剧下线结果
     */
    public static DyResult<DyPlayletOnlineResult> setPlayletAlbumOffline(String token, long album_id) {
        return setPlayletAlbumOffline(token, album_id, 0);
    }

    /**
     * 短剧内容库：短剧上线
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#LcDKdjDnEomNETx26F3cYOJXnc8
     * @param token 接口调用凭据
     * @param album_id 短剧id
     * @param version 版本号
     * @return 短剧上线结果
     */
    public static DyResult<DyPlayletOnlineResult> setPlayletAlbumOnline(String token, long album_id, int version) {
        return setPlayletAlbumOnline(token, album_id, 2, version);
    }

    /**
     * 短剧内容库：短剧下线
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#LcDKdjDnEomNETx26F3cYOJXnc8
     * @param token 接口调用凭据
     * @param album_id 短剧id
     * @param version 版本号
     * @return 短剧下线结果
     */
    public static DyResult<DyPlayletOnlineResult> setPlayletAlbumOffline(String token, long album_id, int version) {
        return setPlayletAlbumOnline(token, album_id, 3, version);
    }

    /**
     * 短剧内容库：短剧上下线/查询线上版本
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#LcDKdjDnEomNETx26F3cYOJXnc8
     * @param token 接口调用凭据
     * @param album_id 短剧id
     * @param operate 操作：1-查询；2-上线；3-下线
     * @param version 上线的版本；查询线上版本不用传此参数
     * @return 短剧上下线结果
     */
    public static DyResult<DyPlayletOnlineResult> setPlayletAlbumOnline(String token, long album_id, int operate, int version) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("album_id", album_id);
        data.put("operate", operate);
        if (version > 0) data.put("version", version);

        String result = post(PLAYLET_ALBUM_ONLINE_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：短剧上下线结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyPlayletOnlineResult>>() {});
    }


    /**
     * 短剧内容库：查询小程序下所有短剧信息
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#JZ0vdNSxUob8E3xzRplc4YY6nZc
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param offset 分页拉取的起始偏移量
     * @param limit 分页拉取的最大返回结果数
     * @return 小程序下所有短剧信息结果
     */
    public static DyResult<DyPlayletAlbumFetchResult> fetchPlayletAlbum(String token, String ma_app_id, int offset, int limit) {
        Map<String, Object> batch_query = new HashMap<String, Object>() {{ put("offset", offset); put("limit", limit); }};
        return fetchPlayletAlbum(token, ma_app_id, 1, batch_query, null, null);
    }

    /**
     * 短剧内容库：查询一个短剧所有版本信息
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#JZ0vdNSxUob8E3xzRplc4YY6nZc
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param album_id 短剧id
     * @param offset 分页拉取的起始偏移量
     * @param limit 分页拉取的最大返回结果数
     * @return 一个短剧所有版本信息结果
     */
    public static DyResult<DyPlayletAlbumFetchResult> fetchPlayletAlbum(String token, String ma_app_id, long album_id, int offset, int limit) {
        Map<String, Object> single_query = new HashMap<String, Object>() {{ put("album_id", album_id); put("offset", offset); put("limit", limit); }};
        return fetchPlayletAlbum(token, ma_app_id, 2, null, single_query, null);
    }

    /**
     * 短剧内容库：查询一个短剧某个版本下所有剧集信息
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#JZ0vdNSxUob8E3xzRplc4YY6nZc
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param album_id 短剧id
     * @param version 版本号
     * @param offset 分页拉取的起始偏移量
     * @param limit 分页拉取的最大返回结果数
     * @return 一个短剧某个版本下所有剧集信息结果
     */
    public static DyResult<DyPlayletAlbumFetchResult> fetchPlayletAlbum(String token, String ma_app_id, long album_id, int version, int offset, int limit) {
        Map<String, Object> detail_query = new HashMap<String, Object>() {{ put("album_id", album_id); put("version", version); put("offset", offset); put("limit", limit); }};
        return fetchPlayletAlbum(token, ma_app_id, 3, null, null, detail_query);
    }

    /**
     * 短剧内容库：短剧查询
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#JZ0vdNSxUob8E3xzRplc4YY6nZc
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param query_type 查询类型：1-查询小程序下所有短剧信息；2-查询一个短剧所有版本信息；3-查询一个短剧某个版本下所有剧集信息
     * @param batch_query 查询小程序下所有短剧信息，查询类型为 1 时必填
     * @param single_query 查询一个短剧所有版本信息，查询类型为 2 时必填
     * @param detail_query 查询一个短剧某个版本下所有剧集信息，查询类型为 3 时必填
     * @return 短剧查询结果
     */
    public static DyResult<DyPlayletAlbumFetchResult> fetchPlayletAlbum(String token, String ma_app_id, int query_type, Map<String, Object> batch_query, Map<String, Object> single_query, Map<String, Object> detail_query) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("ma_app_id", ma_app_id);
        data.put("query_type", query_type);
        if (batch_query != null && !batch_query.isEmpty()) data.put("batch_query", batch_query);
        if (single_query != null && !single_query.isEmpty()) data.put("single_query", single_query);
        if (detail_query != null && !detail_query.isEmpty()) data.put("detail_query", detail_query);

        String result = post(PLAYLET_ALBUM_FETCH_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：短剧查询结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyPlayletAlbumFetchResult>>() {});
    }


    /**
     * 短剧内容库：设置短剧别名（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#N0qwdf2xroAFg1xXY7wcOhy4nHe
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param album_id 短剧id
     * @param operate 操作类型：0-查看短剧别名；1-添加短剧别名；2-删除短剧别名
     * @param alias_names 要增加的短剧别名，送审的短剧标题名称/短剧别名和广告投放短剧名称需保持一致
     * @return 设置短剧别名结果
     */
    public static DyResult<DyPlayletAliasResult> bindPlayletAlias(String token, String ma_app_id, long album_id, int operate, List<String> alias_names) {
        return bindPlayletAlias(token, ma_app_id, album_id, operate, alias_names.toArray(new String[0]));
    }

    /**
     * 短剧内容库：设置短剧别名（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#N0qwdf2xroAFg1xXY7wcOhy4nHe
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param album_id 短剧id
     * @param operate 操作类型：0-查看短剧别名；1-添加短剧别名；2-删除短剧别名
     * @param alias_names 要增加的短剧别名，送审的短剧标题名称/短剧别名和广告投放短剧名称需保持一致
     * @return 设置短剧别名结果
     */
    public static DyResult<DyPlayletAliasResult> bindPlayletAlias(String token, String ma_app_id, long album_id, int operate, String ...alias_names) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("ma_app_id", ma_app_id);
        data.put("operate", operate);
        data.put("alias_names", alias_names);

        String result = post(PLAYLET_ALIAS_BIND_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：设置短剧别名结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyPlayletAliasResult>>() {});
    }


    /**
     * 短剧内容库：获取短剧播放链接
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#CovydK5ESo0XPCxAG9zcNhz8nLb
     * @param token 接口调用凭据
     * @param ma_app_id 小程序id
     * @param album_id 短剧id
     * @param episode_id 剧集id
     * @return 短剧播放链接结果
     */
    public static DyResult<DyPlayletVideoPlayInfo> getPlayletVideoPlayInfo(String token, String ma_app_id, long album_id, long episode_id) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("ma_app_id", ma_app_id);
        data.put("album_id", album_id);
        data.put("episode_id", episode_id);

        String result = post(PLAYLET_VIDEO_PLAY_INFO_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：短剧播放链接结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyPlayletVideoPlayInfo>>() {});
    }


    /**
     * 短剧内容库：获取短剧后置审核
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#L0rBdJn2zoS8eGx3yRQcMm1CnTg
     * @param token 接口调用凭据
     * @param content_id 短剧id
     * @param sub_content_id 剧集id
     * @return 短剧后置审核结果
     */
    public static DyResult<DyResourceLibraryPostReviewResult> queryResourceLibraryPostReview(String token, String content_id, String sub_content_id) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("content_id", content_id);
        data.put("sub_content_id", sub_content_id);

        String result = post(RESOURCE_LIBRARY_POST_REVIEW_QUERY_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：短剧后置审核结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyPlayletVideoPlayInfo>>() {});
    }


    /**
     * 短剧内容库：小程序维度path绑定（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#DldHd393hoC8AhxIyIncLpjXn1D
     * @param token 接口调用凭据
     * @param path 页面路径
     * @return 小程序维度path绑定结果
     */
    public static DyResult<Object> bindResourceLibraryAppPath(String token, String path) {
        return bindResourceLibraryAppPath(token, new DyResourceLibraryAppPathInfo(path));
    }

    /**
     * 短剧内容库：小程序维度path绑定（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#DldHd393hoC8AhxIyIncLpjXn1D
     * @param token 接口调用凭据
     * @param pathInfo 页面信息，小程序维度绑定的path，在搜索和feed卡推荐或其他场景中，以下参数由平台注入拼接，用于跳转到对应剧集播放页面
     * @return 小程序维度path绑定结果
     */
    public static DyResult<Object> bindResourceLibraryAppPath(String token, DyResourceLibraryAppPathInfo pathInfo) {
        Map<String, Object> path_info = JSONObject.parseObject(JSONObject.toJSONString(pathInfo), new TypeReference<Map<String, Object>>() {});
        return bindResourceLibraryAppPath(token, path_info);
    }

    /**
     * 短剧内容库：小程序维度path绑定（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#DldHd393hoC8AhxIyIncLpjXn1D
     * @param token 接口调用凭据
     * @param path_info 页面信息，小程序维度绑定的path，在搜索和feed卡推荐或其他场景中，以下参数由平台注入拼接，用于跳转到对应剧集播放页面
     * @return 小程序维度path绑定结果
     */
    public static DyResult<Object> bindResourceLibraryAppPath(String token, Map<String, Object> path_info) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("path_info", path_info);

        String result = post(RESOURCE_LIBRARY_APP_PATH_BIND_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：小程序维度path绑定结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<Object>>() {});
    }


    /**
     * 短剧内容库：查询小程序维度path绑定（小程序）
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#SKfAdAVRioLe2GxtkTQckrLqnEc
     * @param token 接口调用凭据
     * @return 查询小程序维度path绑定结果
     */
    public static DyResult<DyResourceLibraryAppPathInfo> queryResourceLibraryAppPath(String token) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        String result = post(RESOURCE_LIBRARY_APP_PATH_QUERY_URL, "", DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：查询小程序维度path绑定结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyResourceLibraryAppPathInfo>>() {});
    }


    /**
     * 短剧内容库：设置短剧用户行为
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#PtetdXMDGoE9TtxffIWctIi5noh
     * @param token 接口调用凭据
     * @param open_id 关联小程序下具体用户
     * @param album_id 短剧id
     * @param action_type 事件类型，表示剧订阅行为；action_type 和 action都填2，表示取消追剧
     * @param action 具体动作，2表示取消；action_type 和 action都填2，表示取消追剧
     * @return 短剧用户行为结果
     */
    public static DyResult<Object> setResourceLibraryUserAction(String token, String open_id, String album_id, int action_type, int action) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("open_id", open_id);
        data.put("album_id", album_id);
        data.put("action_type", action_type);
        data.put("action", action);

        String result = post(RESOURCE_LIBRARY_USER_ACTION_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：短剧用户行为结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<Object>>() {});
    }


    /**
     * 短剧内容库：短剧视频同步抖音云
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#WVLBdOhJxoxzyPx4hyrcDAKBn0e
     * @param token 接口调用凭据
     * @param sync_mode 同步模式：1-剧维度同步，此时需传入album_id；2-视频维度同步，此时需传入open_video_id
     * @param album_id 短剧id，同步模式为 1 时有效
     * @param open_video_id 视频id，同步模式为 2 时有效
     * @return 短剧视频同步抖音云结果
     */
    public static DyResult<Object> syncPlayletVideoToCloud(String token, int sync_mode, long album_id, String open_video_id) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("sync_mode", sync_mode);
        data.put("album_id", album_id);
        data.put("open_video_id", open_video_id);

        String result = post(PLAYLET_VIDEO_SYNC_CLOUD_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：短剧视频同步抖音云结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<Object>>() {});
    }


    /**
     * 短剧内容库：设置短剧端内分发可见性限制
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#US4hd5LrEo5UI0xPY3fcEHmTnNb
     * @param token 接口调用凭据
     * @param album_id 短剧id，同步模式为 1 时有效
     * @param operate_type 操作类型：1-Block，关闭分发可见性；2-Open，打开分发可见性；3-BlockApp，关闭特定小程序分发可见性，需要先关闭剧维度分发可见性才能操作；4-OpenApp，打开特定小程序分发可见性，需要先关闭剧维度分发可见性才能操作
     * @param app_id_list 需要操作的小程序列表，操作类型为 3 和 4 时有效
     * @return 设置短剧端内分发可见性限制结果
     */
    public static DyResult<Object> setResourceLibraryDistributionOperate(String token, String album_id, int operate_type, List<String> app_id_list) {
        return setResourceLibraryDistributionOperate(token, album_id, operate_type, app_id_list.toArray(new String[0]));
    }

    /**
     * 短剧内容库：设置短剧端内分发可见性限制
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#US4hd5LrEo5UI0xPY3fcEHmTnNb
     * @param token 接口调用凭据
     * @param album_id 短剧id，同步模式为 1 时有效
     * @param operate_type 操作类型：1-Block，关闭分发可见性；2-Open，打开分发可见性；3-BlockApp，关闭特定小程序分发可见性，需要先关闭剧维度分发可见性才能操作；4-OpenApp，打开特定小程序分发可见性，需要先关闭剧维度分发可见性才能操作
     * @param app_id_list 需要操作的小程序列表，操作类型为 3 和 4 时有效
     * @return 设置短剧端内分发可见性限制结果
     */
    public static DyResult<Object> setResourceLibraryDistributionOperate(String token, String album_id, int operate_type, String... app_id_list) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("album_id", album_id);
        data.put("operate_type", operate_type);
        data.put("app_id_list", app_id_list);

        String result = post(RESOURCE_LIBRARY_DISTRIBUTION_OPERATE_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：设置短剧端内分发可见性限制结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<Object>>() {});
    }


    /**
     * 短剧内容库：查询短剧端内分发可见性限制
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#S1GNdeKMJo4oynxdnJjcFw16n8g
     * @param token 接口调用凭据
     * @param album_id 短剧id，同步模式为 1 时有效
     * @return 查询短剧端内分发可见性限制结果
     */
    public static DyResult<DyResourceLibraryDistributionResult> queryResourceLibraryDistribution(String token, String album_id) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("album_id", album_id);

        String result = post(RESOURCE_LIBRARY_DISTRIBUTION_QUERY_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：查询短剧端内分发可见性限制结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyResourceLibraryDistributionResult>>() {});
    }


    /**
     * 短剧内容库：查询小程序维度交易权益数据
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#ASKNd2pzpou5rNxSrdCcG0K1nEf
     * @param token 接口调用凭据
     * @param date_list 要查询哪些日期的数据，范围：[T-90, T-1], 格式:20241216
     * @return 查询小程序维度交易权益数据结果
     */
    public static DyResult<DyResourceLibraryIaaTradeDataResult> queryResourceLibraryIaaTradeData(String token, List<String> date_list) {
        return queryResourceLibraryIaaTradeData(token, date_list.toArray(new String[0]));
    }

    /**
     * 短剧内容库：查询小程序维度交易权益数据
     * https://bytedance.larkoffice.com/wiki/SG1MwJyWhiqwUxkkZvjcConanue#ASKNd2pzpou5rNxSrdCcG0K1nEf
     * @param token 接口调用凭据
     * @param date_list 要查询哪些日期的数据，范围：[T-90, T-1], 格式:20241216
     * @return 查询小程序维度交易权益数据结果
     */
    public static DyResult<DyResourceLibraryIaaTradeDataResult> queryResourceLibraryIaaTradeData(String token, String... date_list) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("date_list", date_list);

        String result = post(RESOURCE_LIBRARY_IAA_TRADE_DATA_QUERY_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("短剧内容库：查询小程序维度交易权益数据结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<DyResourceLibraryIaaTradeDataResult>>() {});
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="抖音云-媒资管理">

    /**
     * 抖音云-媒资管理：分页获取媒资列表（小程序）
     * https://developer.open-douyin.com/docs/resource/zh-CN/developer/tools/cloud/guide/industry-solutions/short-drama-media-api#ac0b1b86
     * @param token 接口调用凭据
     * @param page_number 当前的页码（从 0 开始）
     * @param page_size 每一页的条数（最多 20 条/页）
     * @return 分页获取媒资列表
     */
    public static DyResult<List<DyYunOpenVideoInfo>> getYunVocVideoList(String token, int page_number, int page_size) {
        return getYunVocVideoList(token, page_number, page_size, 0, null);
    }

    /**
     * 抖音云-媒资管理：分页获取媒资列表（小程序）
     * https://developer.open-douyin.com/docs/resource/zh-CN/developer/tools/cloud/guide/industry-solutions/short-drama-media-api#ac0b1b86
     * @param token 接口调用凭据
     * @param page_number 当前的页码（从 0 开始）
     * @param page_size 每一页的条数（最多 20 条/页）
     * @param video_name 短剧的名称
     * @return 分页获取媒资列表
     */
    public static DyResult<List<DyYunOpenVideoInfo>> getYunVocVideoList(String token, int page_number, int page_size, String video_name) {
        return getYunVocVideoList(token, page_number, page_size, 0, video_name);
    }

    /**
     * 抖音云-媒资管理：分页获取媒资列表（小程序）
     * https://developer.open-douyin.com/docs/resource/zh-CN/developer/tools/cloud/guide/industry-solutions/short-drama-media-api#ac0b1b86
     * @param token 接口调用凭据
     * @param page_number 当前的页码（从 0 开始）
     * @param page_size 每一页的条数（最多 20 条/页）
     * @param business_status 审核状态：98-审核中；99-未审核；如果不传，则审核和未审核均查询
     * @return 分页获取媒资列表
     */
    public static DyResult<List<DyYunOpenVideoInfo>> getYunVocVideoList(String token, int page_number, int page_size, int business_status) {
        return getYunVocVideoList(token, page_number, page_size, business_status, null);
    }

    /**
     * 抖音云-媒资管理：分页获取媒资列表（小程序）
     * https://developer.open-douyin.com/docs/resource/zh-CN/developer/tools/cloud/guide/industry-solutions/short-drama-media-api#ac0b1b86
     * @param token 接口调用凭据
     * @param page_number 当前的页码（从 0 开始）
     * @param page_size 每一页的条数（最多 20 条/页）
     * @param business_status 审核状态：98-审核中；99-未审核；如果不传，则审核和未审核均查询
     * @param video_name 短剧的名称
     * @return 分页获取媒资列表
     */
    public static DyResult<List<DyYunOpenVideoInfo>> getYunVocVideoList(String token, int page_number, int page_size, int business_status, String video_name) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("page_number", page_number);
        data.put("page_size", page_size);
        if (business_status != 0) data.put("business_status", business_status);
        if (video_name != null && !video_name.trim().isEmpty()) data.put("video_name", video_name);

        String result = post(YUN_VOC_GET_VIDEO_LIST_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("抖音云-媒资管理：分页获取媒资列表结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<List<DyYunOpenVideoInfo>>>() {});
    }


    /**
     * 抖音云-媒资管理：删除媒资（小程序）
     * https://developer.open-douyin.com/docs/resource/zh-CN/developer/tools/cloud/guide/industry-solutions/short-drama-media-api#add30490
     * @param token 接口调用凭据
     * @param vid 媒资ID
     * @param env_id 抖音云 prod 环境的 id，需要登录抖音云控制台，查看
     * @return 删除媒资结果
     */
    public static DyResult<Object> deleteYunVocVideo(String token, String vid, String env_id) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("vid", vid);
        data.put("env_id", env_id);

        String result = post(YUN_VOC_DELETE_VIDEO_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("抖音云-媒资管理：删除媒资结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<Object>() {});
    }


    /**
     * 抖音云-媒资管理：获取媒资播放链接（小程序）
     * https://developer.open-douyin.com/docs/resource/zh-CN/developer/tools/cloud/guide/industry-solutions/short-drama-media-api#a3c7307b
     * @param token 接口调用凭据
     * @param vid 视频资源的唯一id
     * @return 获取媒资播放链接结果
     */
    public static DyResult<List<DyYunOpenVideoUrlInfo>> getYunVocVideo(String token, String vid) {
        return getYunVocVideo(token, vid, null, 0);
    }

    /**
     * 抖音云-媒资管理：获取媒资播放链接（小程序）
     * https://developer.open-douyin.com/docs/resource/zh-CN/developer/tools/cloud/guide/industry-solutions/short-drama-media-api#a3c7307b
     * @param token 接口调用凭据
     * @param vid 视频资源的唯一id
     * @param domain 可以指定媒资管理已有域名，生成播放链接，未指定如设置默认域名，则按指定域名生成，未设置默认域名，则随机选择域名生成
     * @return 获取媒资播放链接结果
     */
    public static DyResult<List<DyYunOpenVideoUrlInfo>> getYunVocVideo(String token, String vid, String domain) {
        return getYunVocVideo(token, vid, domain, 0);
    }

    /**
     * 抖音云-媒资管理：获取媒资播放链接（小程序）
     * https://developer.open-douyin.com/docs/resource/zh-CN/developer/tools/cloud/guide/industry-solutions/short-drama-media-api#a3c7307b
     * @param token 接口调用凭据
     * @param vid 视频资源的唯一id
     * @param expire_time 可以指定播放链接的过期时间（秒），取值范围[3600- 14*24*3600] 。默认是 3600
     * @return 获取媒资播放链接结果
     */
    public static DyResult<List<DyYunOpenVideoUrlInfo>> getYunVocVideo(String token, String vid, int expire_time) {
        return getYunVocVideo(token, vid, null, expire_time);
    }

    /**
     * 抖音云-媒资管理：获取媒资播放链接（小程序）
     * https://developer.open-douyin.com/docs/resource/zh-CN/developer/tools/cloud/guide/industry-solutions/short-drama-media-api#a3c7307b
     * @param token 接口调用凭据
     * @param vid 视频资源的唯一id
     * @param domain 可以指定媒资管理已有域名，生成播放链接，未指定如设置默认域名，则按指定域名生成，未设置默认域名，则随机选择域名生成
     * @param expire_time 可以指定播放链接的过期时间（秒），取值范围[3600- 14*24*3600] 。默认是 3600
     * @return 获取媒资播放链接结果
     */
    public static DyResult<List<DyYunOpenVideoUrlInfo>> getYunVocVideo(String token, String vid, String domain, int expire_time) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("vid", vid);
        if (domain != null && !domain.trim().isEmpty()) data.put("domain", domain);
        if (expire_time != 0) data.put("expire_time", expire_time);

        String result = post(YUN_VOC_GET_VIDEO_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("抖音云-媒资管理：获取媒资播放链接结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyResult<List<DyYunOpenVideoUrlInfo>>>() {});
    }


    /**
     * 抖音云-媒资管理：批量上传视频（小程序）
     * https://developer.open-douyin.com/docs/resource/zh-CN/developer/tools/cloud/guide/industry-solutions/short-drama-media-api#add30490
     * @param token 接口调用凭据
     * @param url_sets 源视频的 urlSet 列表，一次最多传入 20 条
     * @return 批量上传视频结果
     */
    public static DyResult<DyYunOpenVideoUrlJobResult> uploadYunVocVideos(String token, List<DyYunOpenVideoUrlSet> url_sets) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("access-token", token);

        JSONObject data = new JSONObject();
        data.put("url_sets", url_sets);

        String result = post(YUN_VOC_UPLOAD_VIDEO_URL, data.toJSONString(), DOUYIN_AGENT, headers);
        System.out.println("抖音云-媒资管理：批量上传视频结果：" + result);
        return JSONObject.parseObject(result, new TypeReference<DyYunOpenVideoUrlJobResult>() {});
    }

    // </editor-fold>

}
